package com.github.byakkili.bim.core.threadpool.support;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import com.github.byakkili.bim.core.support.util.JvmUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.String.format;

/**
 * @author Guannian Li
 */
@Slf4j
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {

    private static final AtomicInteger THREAD_NUM = new AtomicInteger(1);

    private static final int TEN_MINUTES_MILLS = 10 * 60 * 1000;

    private static final String WIN_DATETIME_FORMAT = "yyyy-MM-dd_HH-mm-ss";

    private static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd_HH:mm:ss";

    private static final Semaphore GUARD = new Semaphore(1);

    private static final String USER_HOME = SystemUtil.getUserInfo().getHomeDir();

    private final String dumpPath;

    private final String threadName;

    private volatile long lastPrintTime = 0;

    public AbortPolicyWithReport(String threadName, String dumpPath) {
        this.dumpPath = dumpPath;
        this.threadName = threadName;
    }

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED! Thread Name: %s, "
                        + "Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), "
                        + "Task: %d (completed: %d), "
                        + "Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s)",
                threadName,
                e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(),
                e.isShutdown(), e.isTerminated(), e.isTerminating());
        log.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }

    private void dumpJStack() {
        long now = System.currentTimeMillis();
        //dump every 10 minutes
        if (now - lastPrintTime < TEN_MINUTES_MILLS) {
            return;
        }
        if (!GUARD.tryAcquire()) {
            return;
        }
        ThreadUtil.newThread(() -> {
            String dumpDir = getDumpPath();

            // window system don't support ":" in file name
            SimpleDateFormat sdf = SystemUtil.getOsInfo().isWindows() ? new SimpleDateFormat(WIN_DATETIME_FORMAT) : new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);

            String dateStr = sdf.format(new Date());
            // try-with-resources
            try (FileOutputStream jStackStream = new FileOutputStream(new File(dumpDir, "BIM_JStack.log." + dateStr))) {
                JvmUtils.jstack(jStackStream);
            } catch (Exception e) {
                log.error("dump jStack error", e);
            } finally {
                GUARD.release();
            }
            lastPrintTime = System.currentTimeMillis();
        }, "Abort-Policy-With-Report-" + THREAD_NUM.getAndIncrement()).start();
    }

    private String getDumpPath() {
        if (StrUtil.isEmpty(dumpPath)) {
            return USER_HOME;
        }
        final File dumpDirectory = new File(dumpPath);
        if (!dumpDirectory.exists()) {
            if (dumpDirectory.mkdirs()) {
                log.info(format("BIM dump directory[%s] created", dumpDirectory.getAbsolutePath()));
            } else {
                log.warn(format("BIM dump directory[%s] can't be created, use the 'user.home'[%s]", dumpDirectory.getAbsolutePath(), USER_HOME));
                return USER_HOME;
            }
        }
        return dumpPath;
    }
}
